package avicit.bdp.oss.service.object;

import avicit.bdp.common.base.BaseService;
import avicit.bdp.common.utils.BdpLogUtil;
import avicit.bdp.common.utils.ConfigUtils;
import avicit.bdp.common.utils.file.MinioUtils;
import avicit.bdp.oss.dao.ObjectDao;
import avicit.bdp.oss.dto.BucketAuditDto;
import avicit.bdp.oss.dto.BucketAuthDto;
import avicit.bdp.oss.dto.BucketDto;
import avicit.bdp.oss.dto.ObjectDto;
import avicit.bdp.oss.service.auth.BucketAuthService;
import avicit.bdp.oss.service.bucket.BucketService;
import avicit.bdp.oss.service.stats.BucketAuditService;
import avicit.bdp.oss.utils.auth.PermissionCheckUtils;
import avicit.bdp.oss.utils.auth.StrategyEnum;
import avicit.bdp.oss.utils.common.AcitonEnum;
import avicit.bdp.oss.utils.common.CheckParaUtils;
import avicit.bdp.oss.utils.common.Constants;
import avicit.bdp.oss.utils.stats.MathUtils;
import avicit.bdp.oss.utils.upload.entity.OssUploadDto;
import avicit.platform6.api.system.ConvertColumnClient;
import avicit.platform6.api.system.impl.SystemConstant;
import avicit.platform6.commons.utils.BusinessUtil;
import avicit.platform6.commons.utils.ComUtil;
import avicit.platform6.core.context.ThreadContextHelper;
import avicit.platform6.core.exception.BusinessException;
import avicit.platform6.core.rest.msg.QueryRespBean;
import com.github.pagehelper.Page;
import io.minio.MinioClient;
import org.apache.commons.codec.CharEncoding;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @金航数码科技有限责任公司
 * @作者：developer
 * @邮箱：developer@avic-digital.com
 * @创建时间：2020-11-12
 * @类说明：ObjectService
 * @修改记录：
 * @注意事项：
 */
@Service
@Transactional
public class ObjectService extends BaseService<ObjectDao, ObjectDto> {
  private static final Logger logger = LoggerFactory.getLogger(ObjectService.class);

  /** Minio连接信息 */
  private String endPoint;
  private String accessKey;
  private String secretKey;

  /** Object的Dao层 */
  @Autowired private ObjectDao objectDao;

  /** Bucket相关接口 */
  @Autowired private BucketService bucketService;

  /** Bucket审计日志相关接口 */
  @Autowired private BucketAuditService bucketAuditService;

  /** 对象-用户映射关系 */
  @Autowired private BucketAuthService bucketAuthService;

  /** Minio客户端 */
  MinioClient minioClient;

  /** 获取系统用户名 */
  @Autowired private ConvertColumnClient convertColumnClient;

  /**
   * 新建目录
   *
   * @param objectDto
   */
  public void makeDirectory(ObjectDto objectDto) {
    // step1:合法性检查
    CheckParaUtils.checkObjectValidity(objectDto, logger, "新建目录失败，参数为null.");
    CheckParaUtils.checkStringValidity(objectDto.getObjectName(), logger, "新建目录失败，目录名称为null.");
    CheckParaUtils.checkStringValidity(objectDto.getBucketName(), logger, "新建目录失败，桶名称为null.");
    String prefix = objectDto.getPrefix() == null ? Constants.BLACK : objectDto.getPrefix();
    checkObjectExist(objectDto.getBucketName(), objectDto.getPrefix(), objectDto.getObjectName());
    checkObjectNameValid(objectDto.getObjectName());

    // step2:获取元数据信息
    BucketDto queryBucketRet = queryBucketDto(objectDto.getBucketName());

    // step3:权限、密级检查(此处仅需要检查用户对桶是否有操作权限)
    PermissionCheckUtils.checkPerm(queryBucketRet, null, null, AcitonEnum.MAKE_DIRECTORY);

    // step4:元数据持久化
    ObjectDto newObjectDto =
        ObjectDto.builder()
            .id(ComUtil.getId())
            .objectName(objectDto.getObjectName())
            .prefix(prefix)
            .bucketName(objectDto.getBucketName())
            .isDirectory(true)
            .deleteFlag(false)
            .build();
    insert(newObjectDto);

    // step5:创建目录(实际创建的还是文件对象，只不过以"/"结尾，模拟了文件夹或目录)
    MinioUtils.putObject(
        getMinioClient(),
        objectDto.getBucketName(),
        MinioUtils.buildDirectoryName(prefix, objectDto.getObjectName()),
        new ByteArrayInputStream(new byte[] {}));

    // step6:记录平台日志
    BdpLogUtil.log4Insert(newObjectDto);
  }

  /**
   * 列举对象
   *
   * @param sort
   * @param pageNo
   * @param pageSize
   * @param bucketName
   * @param prefix
   * @param objectName
   * @return
   */
  public QueryRespBean<ObjectDto> listObjects(
      Integer sort,
      Integer pageNo,
      Integer pageSize,
      String bucketName,
      String prefix,
      String objectName) {
    // step1:合法性检查
    if (sort == null) {
      sort = new Integer(1);
    }
    if (sort < Constants.ORDERED_BY_MODIFIED_TIME_DESC
        || sort > Constants.ORDERED_BY_BUCKET_NAME_DESC) {
      CheckParaUtils.printLogAndThrowException(
          logger, String.format("查询对象列表时，参数sort[%s]不合法", sort));
    }
    if (pageNo <= 0 || pageSize <= 0) {
      CheckParaUtils.printLogAndThrowException(
          logger, String.format("查询对象列表时，参数pageNo/pageSize[%s,%s]不合法", pageNo, pageSize));
    }
    CheckParaUtils.checkStringValidity(bucketName, logger, "查询对象列表时，桶名称为null.");

    // step2:查表
    QueryRespBean<ObjectDto> queryRespBean =
        exeQueryObjectList(sort, pageNo, pageSize, bucketName, prefix, objectName);

    // step3:设置文件大小及单位
    for (ObjectDto objectDto : queryRespBean.getResult().getResult()) {
      objectDto.setFileSize(MathUtils.storageUnitProcess(objectDto.getObjectSize()));
      objectDto.setCreatedBy(getUserNameByIds(objectDto.getCreatedBy()));
    }

    BdpLogUtil.log4Query(queryRespBean);
    return queryRespBean;
  }

  /**
   * 上传对象
   *
   * @param objectDto
   */
  public void putObject(ObjectDto objectDto) {
    // step1:合法性检查
    CheckParaUtils.checkObjectValidity(objectDto, logger, "上传对象失败，参数为null");
    CheckParaUtils.checkObjectValidity(objectDto.getFile(), logger, "上传对象失败，参数[file]不能为空");
    CheckParaUtils.checkStringValidity(objectDto.getBucketName(), logger, "上传对象失败，桶名称为null.");

    // step2:获取元数据
    MultipartFile file = objectDto.getFile();
    String objectName = file.getResource().getFilename();
    String prefix = objectDto.getPrefix() == null ? Constants.BLACK : objectDto.getPrefix();
    ObjectDto queryObjectRet = queryObjectDto(objectDto.getBucketName(), prefix, objectName, false);
    BucketDto queryBucketRet = queryBucketDto(objectDto.getBucketName());
    BucketAuthDto queryBucketAuthRet = null;
    if (queryObjectRet != null) {
      queryBucketAuthRet = queryBucketAuthByObjectId(queryObjectRet.getId());
    }

    // step3:权限检查
    PermissionCheckUtils.checkPerm(
        queryBucketRet, queryObjectRet, queryBucketAuthRet, AcitonEnum.PUT_OBJECT);

    // step3:更新元数据信息
    try {
      InputStream inputStream = file.getInputStream();
      CheckParaUtils.checkObjectValidity(inputStream, logger, "上传对象失败, 参数[file]中inputStream字段不能为空");
      if (queryObjectRet != null) {
        // 文件已存在，直接覆盖
        logger.info(String.format("[%s-%s]已存在，直接覆盖."));

        // step3-1:统计概要信息（注意:重复上传时修订容量变化、objectNum未变化）
        recordStatsInfo(
            queryBucketRet,
            queryObjectRet.getId(),
            queryObjectRet.getObjectSize(),
            file.getSize(),
            AcitonEnum.REPEAT_PUT_OBJECT);

        // step3-2:持久化
        queryObjectRet.setObjectSize(file.getSize());
        updateByPrimaryKey(queryObjectRet);

        // step5:记录平台日志
        BdpLogUtil.log4Update(queryObjectRet, objectDto);
      } else {
        // 文件不存在，新建保存
        // step3-3:持久化
        ObjectDto newObjectDto =
            ObjectDto.builder()
                .id(ComUtil.getId())
                .objectName(objectName)
                .objectSize(file.getSize())
                .prefix(prefix)
                .bucketName(objectDto.getBucketName())
                .isDirectory(false)
                .build();
        insert(newObjectDto);

        // step3-4:统计概要信息
        recordStatsInfo(
            queryBucketRet,
            newObjectDto.getId(),
            0L,
            newObjectDto.getObjectSize(),
            AcitonEnum.PUT_OBJECT);

        // step3-5:记录平台日志
        BdpLogUtil.log4Insert(newObjectDto);
      }

      // step4:调用OSS集群接口，上传文件
      MinioUtils.putObject(
          getMinioClient(),
          objectDto.getBucketName(),
          MinioUtils.buildFileName(prefix, objectName),
          inputStream);
    } catch (IOException e) {
      logger.error(String.format("上传对象[%s-%s]失败, errMsg=%s", e));
    }
  }

  /**
   * 下载对象
   *
   * @param id
   * @param response
   */
  public void getObject(String id, HttpServletRequest request, HttpServletResponse response) {
    // step1:合法性检查
    CheckParaUtils.checkObjectValidity(id, logger, "下载对象失败，参数[id]不能为空");

    // step2:获取对象元数据
    ObjectDto queryObjectRet = queryObjectDto(id, true);
    BucketDto queryBucketRet = queryBucketDto(queryObjectRet.getBucketName());
    BucketAuthDto queryBucketAuthRet = queryBucketAuthByObjectId(id);

    // step3:权限、密级检查
    PermissionCheckUtils.checkPerm(
        queryBucketRet, queryObjectRet, queryBucketAuthRet, AcitonEnum.GET_OBJECT);

    // step4:获取数据流
    InputStream in = null;
    BufferedOutputStream out = null;
    try {
      // step4-1:根据bucketName、objectName获取inputStream
      String prefix = queryObjectRet.getPrefix();
      String objectName = queryObjectRet.getObjectName();
      in =
          MinioUtils.getObject(
              getMinioClient(),
              queryObjectRet.getBucketName(),
              MinioUtils.buildFileName(prefix, objectName));

      logger.info("开始下载对象：[桶%s-对象%s]...", queryObjectRet.getBucketName(), objectName);

      // step4-2:获取断点位置
      long point = 0;
      long fileLength = queryObjectRet.getObjectSize();
      long end = fileLength;
      String range = request.getHeader(HttpHeaders.RANGE);
      if (range != null) {
        response.setStatus(javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT);

        String RangeFromTo = range.replaceAll("bytes=", "");
        String[] Ranges = RangeFromTo.split("-");
        point = Long.parseLong(Ranges[0]);
        if ((Ranges.length == 2) && (!"".equals(Ranges[1]))) {
          end = Long.parseLong(Ranges[1]);
        }
      }

      // step4-3:获取断点位置，设置支持断点、本次下载对象大小、编码方式、返回数据起止位置
      String contentRange =
          new StringBuffer("bytes ")
              .append(new Long(point).toString())
              .append("-")
              .append(new Long(fileLength - 1).toString())
              .append("/")
              .append(new Long(fileLength).toString())
              .toString();
      if (point != 0) {
        contentRange =
            new StringBuffer("bytes ")
                .append(new Long(point).toString())
                .append("-")
                .append(new Long(end - 1).toString()) //
                .append("/")
                .append(new Long(fileLength).toString())
                .toString();

        in.skip(point);
      }
      response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
      response.setHeader(HttpHeaders.CONTENT_LENGTH, new Long(end - point).toString());
      response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
      response.setHeader(HttpHeaders.CONTENT_RANGE, contentRange);

      // step4-4:设置文件ContentType类型、下载对象名称
      String fileName = objectName;
      fileName = URLEncoder.encode(fileName, CharEncoding.UTF_8);
      fileName = fileName.replace("+", "%20"); // 解决URLEncoder.encode空格变+号问题
      response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName);

      // step4-5:返回数据流
      out = new BufferedOutputStream(response.getOutputStream());
      long bytesWritten = 0;
      byte[] bytes = new byte[Constants.BUFFER_SIZE];
      int byteCount = 0;
      long NeedWriten = end - point + 1;
      while (NeedWriten >= bytesWritten && (byteCount = in.read(bytes)) != -1) {
        if (NeedWriten >= bytesWritten) {
          long tTempWriten =
              (bytesWritten + byteCount) > NeedWriten ? (NeedWriten - bytesWritten) : byteCount;
          out.write(bytes, 0, (int) tTempWriten);
          bytesWritten += tTempWriten;
        }
      }
      out.flush();
    } catch (Exception e) {
      logger.error(String.format("下载对象失败，errMsg=%s", e.getMessage()));
      throw new BusinessException(e.getMessage());
    } finally {
      try {
        if (in != null) {
          in.close();
        }
        if (out != null) {
          out.close();
        }
      } catch (IOException ioe) {
        logger.error(String.format("下载对象失败，errMsg=%s", ioe.getMessage()));
      }
    }

    // step5:统计概要信息
    recordStatsInfo(
        queryBucketRet,
        queryObjectRet.getId(),
        0L,
        queryObjectRet.getObjectSize(),
        AcitonEnum.GET_OBJECT);

    logger.info("下载对象完成%s-%s", queryObjectRet.getBucketName(), queryObjectRet.getObjectName());
  }

  /**
   * 获取对象属性信息
   *
   * @param id
   * @return
   */
  public ObjectDto statObject(String id) {
    // step1:合法性检查
    CheckParaUtils.checkStringValidity(id, logger, "获取对象属性失败，对象ID为null.");

    // step2:获取元数据
    ObjectDto queryObjectRet = queryObjectDto(id, true);
    BucketDto queryBucketRet = queryBucketDto(queryObjectRet.getBucketName());
    BucketAuthDto queryBucketAuthRet = queryBucketAuthByObjectId(id);

    // step3:权限、密级检查
    PermissionCheckUtils.checkPerm(
        queryBucketRet, queryObjectRet, queryBucketAuthRet, AcitonEnum.STAT_OBJECT);

    return queryObjectRet;
  }

  /**
   * 删除对象（包括文件、目录）
   *
   * @param objectDto
   * @return
   */
  public void removeObject(ObjectDto objectDto) {
    // step1:合法性检查
    CheckParaUtils.checkObjectValidity(objectDto, logger, "删除对象失败，参数为null.");
    CheckParaUtils.checkStringValidity(objectDto.getId(), logger, "删除对象失败，目录ID为null.");

    // step2:遍历id，循环删除对象
    String[] tmpIds = objectDto.getId().split(Constants.COMMA);
    for (String currId : tmpIds) {
      // step2-1:获取元数据
      ObjectDto queryObjectRet = queryObjectDto(currId, true);
      BucketDto queryBucketRet = queryBucketDto(queryObjectRet.getBucketName());
      BucketAuthDto queryBucketAuthRet = queryBucketAuthByObjectId(currId);

      // step2-2:权限、密级检查
      PermissionCheckUtils.checkPerm(
          queryBucketRet, queryObjectRet, queryBucketAuthRet, AcitonEnum.REMOVE_OBJECT);

      // step2-3:删除对象
      recursiveDeleteObject(queryBucketRet, queryObjectRet);

      // step2-4:记录平台日志
      BdpLogUtil.log4Delete(queryObjectRet);
    }
  }

  /**
   * 分享对象
   *
   * @param id
   * @param expire，url过期时间，max=7天，min=60s，单位是秒
   * @return
   */
  public String shareObject(HttpServletRequest request, String id, Integer expire) {
    // step1:合法性检查
    CheckParaUtils.checkStringValidity(id, logger, "分享对象失败，对象ID为null.");

    // step2:获取元数据
    ObjectDto queryObjectRet = queryObjectDto(id, true);
    BucketDto queryBucketRet = queryBucketDto(queryObjectRet.getBucketName());
    BucketAuthDto queryBucketAuthRet = queryBucketAuthByObjectId(id);

    // step3:权限、密级检查
    PermissionCheckUtils.checkPerm(
        queryBucketRet, queryObjectRet, queryBucketAuthRet, AcitonEnum.SHARE_OBJECT);

    // step4:调用OSS存储集群接口，获取对象URL
//    String objectPath = MinioUtils.getObjectUrl(
//            getMinioClient(),
//            queryObjectRet.getBucketName(),
//            MinioUtils.buildFileName(queryObjectRet.getPrefix(), queryObjectRet.getObjectName()),
//            calExpireTime(expire));

    StringBuilder sb = new StringBuilder();
    String referer = request.getHeader("Referer");
    if (StringUtils.isNotEmpty(referer)) {
      try {
        URL url = new URL(referer);
        String ip = url.getHost();
        int port = url.getPort();
        String domain = url.getPath().split("/")[1];

        sb.append("http://").append(ip).append(":" + port).append("/").append(domain);
      } catch (Exception e) {
        logger.error("获取对象分享链接失败", e);
        return null;
      }

      sb.append("/api/bdp/oss/object/getObject?id=").append(id);
      return sb.toString();
    }

    return null;
  }

  /**
   * 根据bucketName、objectName下载对象
   *
   * @param bucketName
   * @param objectName
   * @param response
   */
  public void getObjectByName(String bucketName, String objectName, HttpServletResponse response) {
    // step1:合法性检查
    MinioUtils.isValidBucketName(bucketName);
    CheckParaUtils.checkObjectValidity(objectName, logger, "下载对象失败，参数[objectName]不能为空");
    CheckParaUtils.checkObjectValidity(response, logger, "下载对象失败，参数[response]不能为空");

    // step2:获取元数据
    String prefix = MinioUtils.getPrefixFromObjectName(objectName);
    String realObjectName = MinioUtils.getNameFromObjectName(objectName);
    ObjectDto queryObjectRet = queryObjectDto(bucketName, prefix, realObjectName, true);
    BucketDto queryBucketRet = queryBucketDto(queryObjectRet.getBucketName());
    BucketAuthDto queryBucketAuthRet = queryBucketAuthByObjectId(queryObjectRet.getId());

    // step3:权限、密级检查
    PermissionCheckUtils.checkPerm(
        queryBucketRet, queryObjectRet, queryBucketAuthRet, AcitonEnum.GET_OBJECT);

    // step4:获取数据流
    InputStream inputStream = null;
    OutputStream outputStream = null;
    try {
      inputStream = MinioUtils.getObject(getMinioClient(), bucketName, objectName);

      logger.info("下载的文件名称为：[%s-%s]", bucketName, objectName);

      String fileName = realObjectName;
      // 设置文件ContentType类型，这样设置，会自动判断下载文件类型
      response.setContentType("multipart/form-data");
      // 设置文件头：最后一个参数是设置下载文件名
      fileName = URLEncoder.encode(fileName, "utf-8");
      response.setHeader("Content-Disposition", "attachment;filename=" + fileName);

      // 返回数据流
      outputStream = response.getOutputStream();
      byte[] b = new byte[Constants.FILE_WRITER_BUFFER_SIZE];
      int len;
      while ((len = inputStream.read(b)) > 0) {
        outputStream.write(b, 0, len);
      }

      outputStream.flush();
    } catch (Exception e) {
      try {
        if (inputStream != null) {
          inputStream.close();
        }
        if (outputStream != null) {
          outputStream.close();
        }
      } catch (IOException ioe) {
        logger.error(String.format("下载对象失败，errMsg=%s", ioe));
      }
    }

    // step5:统计概要信息
    recordStatsInfo(
        queryBucketRet,
        queryObjectRet.getId(),
        0L,
        queryObjectRet.getObjectSize(),
        AcitonEnum.GET_OBJECT);
  }

  /**
   * 列举对象(只返回对象，不返回目录)
   *
   * @param bucketName
   * @param prefix
   * @return
   */
  public List<String> getObjects(String bucketName, String prefix) {
    List<String> objectNames = new ArrayList<>();

    // step1:合法性检查
    CheckParaUtils.checkStringValidity(bucketName, logger, "查询对象列表时，桶名称为null.");

    // step2:构建排序参数
    com.github.pagehelper.PageHelper.startPage(1, Constants.MAX_PAGE_SIZE);
    String orderField = Constants.ORDERED_BY_LAST_UPDATE_TIME;
    String orderType = Constants.ORDERED_DESC;

    // step3:查表
    prefix = prefix == null ? Constants.BLACK : prefix;
    Page<ObjectDto> objectList =
        this.objectDao.getObjectList(orderField, orderType, bucketName, prefix, null);

    // step4:设置对象名称prefix+objectName
    for (ObjectDto objectDto : objectList.getResult()) {
      if (!objectDto.isDirectory()) {
        objectNames.add(MinioUtils.buildFileName(objectDto.getPrefix(), objectDto.getObjectName()));
      }
    }

    return objectNames;
  }

  /**
   * 查询系统中最大的五个对象信息 对外接口，提供给其他模块适用
   *
   * @return
   */
  public List<ObjectDto> getMaxObjects() {
    // step:扫描数据库，获取最大的五个对象信息
    List<ObjectDto> objectDtos = this.objectDao.getMaxObjects();

    // step2:单位换算
    for (ObjectDto objectDto : objectDtos) {
      objectDto.setFileSize(MathUtils.storageUnitProcess(objectDto.getObjectSize()));
    }

    return objectDtos;
  }

  /**
   * 保存上传对象元数据
   *
   * @param ossUploadDto
   */
  public synchronized void saveUploadedInfo(OssUploadDto ossUploadDto) {
    // step1:获取元数据
    String bucketName = ossUploadDto.getBucketName();
    String prefix = ossUploadDto.getPrefix() == null ? Constants.BLACK : ossUploadDto.getPrefix();
    String objectName = ossUploadDto.getObjectName();
    ObjectDto queryObjectRet = queryObjectDto(bucketName, prefix, objectName, false);
    BucketDto queryBucketRet = queryBucketDto(ossUploadDto.getBucketName());

    // step2:更新元数据信息
    if (queryObjectRet != null) {
      // 文件已存在，直接覆盖
      logger.info(String.format("[%s-%s]已存在，直接覆盖.", bucketName, objectName));

      // step2-1:统计概要信息（注意:重复上传时修订容量变化、objectNum未变化）
      recordStatsInfo(
          queryBucketRet,
          queryObjectRet.getId(),
          queryObjectRet.getObjectSize(),
          ossUploadDto.getObjectSize(),
          AcitonEnum.REPEAT_PUT_OBJECT);

      // step2-2:持久化
      queryObjectRet.setObjectSize(ossUploadDto.getObjectSize());
      updateByPrimaryKey(queryObjectRet);
    } else {
      // 文件不存在，新建保存
      // step2-3:持久化
      ObjectDto newObjectDto =
          ObjectDto.builder()
              .id(ComUtil.getId())
              .bucketName(bucketName)
              .prefix(prefix)
              .objectName(objectName)
              .objectSize(ossUploadDto.getObjectSize())
              .isDirectory(false)
              .build();
      insert(newObjectDto);

      // step3-4:统计概要信息
      recordStatsInfo(
          queryBucketRet,
          newObjectDto.getId(),
          0L,
          newObjectDto.getObjectSize(),
          AcitonEnum.PUT_OBJECT);
    }
  }

  /**
   * 获取Object
   *
   * @param bucketName
   * @param prefix
   * @param objectName
   * @return
   */
  public ObjectDto getObject(String bucketName, String prefix, String objectName) {
    return this.objectDao.getObjectByName(
        bucketName, prefix == null ? Constants.BLACK : prefix, objectName);
  }

  /**
   * 查询ObjectDto
   *
   * @param id
   * @return
   */
  public ObjectDto queryObjectDto(String id, boolean isNotNull) {
    ObjectDto queryObjectRet = this.objectDao.selectByPrimaryKey(id);
    if (isNotNull) {
      CheckParaUtils.checkObjectValidity(queryObjectRet, logger, String.format("对象[%s]不存在.", id));
    }

    return queryObjectRet;
  }

  /**
   * 查询BucketDto
   *
   * @param bucketName
   * @return
   */
  public BucketDto queryBucketDto(String bucketName) {
    BucketDto queryBucketRet = this.bucketService.getBucketByName(bucketName);
    CheckParaUtils.checkObjectValidity(
        queryBucketRet, logger, String.format("新建目录失败[%s]失败，桶不存在.", bucketName));

    return queryBucketRet;
  }

  /**
   * 查询ObjectDto
   *
   * @param bucketName
   * @param prefix
   * @param objectName
   * @return
   */
  public ObjectDto queryObjectDto(
      String bucketName, String prefix, String objectName, boolean isNotNull) {
    ObjectDto queryObjectRet = this.objectDao.getObjectByName(bucketName, prefix, objectName);
    if (isNotNull) {
      CheckParaUtils.checkObjectValidity(
          queryObjectRet,
          logger,
          String.format("对象[%s-%s-%s]不存在.", bucketName, prefix, objectName));
    }

    return queryObjectRet;
  }

  /**
   * 查询BucketAuthDto
   *
   * @param objectId
   * @return
   */
  public BucketAuthDto queryBucketAuthByObjectId(String objectId) {
    return this.bucketAuthService.queryBucketAuthByObjectId(objectId);
  }

  /**
   * 判断对象是否重复
   *
   * @param bucketName
   * @param prefix
   * @param objectName
   */
  private void checkObjectExist(String bucketName, String prefix, String objectName) {
    ObjectDto queryObjectRet = this.objectDao.getObjectByName(bucketName, prefix, objectName);
    if (queryObjectRet != null) {
      CheckParaUtils.checkObjectValidity(
          queryObjectRet,
          logger,
          String.format("对象[%s-%s-%s]已存在.", bucketName, prefix, objectName));
    }
  }

  /**
   * 获取当前登录用户信息
   *
   * @return
   */
  private String getUserId() {
    return ThreadContextHelper.getUserId() == null
        ? Constants.DEFALUT_USER_ID
        : ThreadContextHelper.getUserId();
  }

  /**
   * 构建orderField
   *
   * @param sort
   * @return
   */
  private String buildOrderField(Integer sort) {
    // 排序，1:按修改时间倒序、2:按修改时间升序、3:按对象名称升序、4:按对象名称倒序
    String orderField = Constants.ORDERED_BY_LAST_UPDATE_TIME;
    if (sort != null) {
      if (Constants.ORDERED_BY_MODIFIED_TIME_DESC == sort) {
        orderField = Constants.ORDERED_BY_LAST_UPDATE_TIME;
      } else if (Constants.ORDERED_BY_MODIFIED_TIME_ASC == sort) {
        orderField = Constants.ORDERED_BY_LAST_UPDATE_TIME;
      } else if (Constants.ORDERED_BY_OBJECT_NAME_ASC == sort) {
        orderField = Constants.ORDERED_BY_OBJECT_NAME;
      } else if (Constants.ORDERED_BY_OBJECT_NAME_DESC == sort) {
        orderField = Constants.ORDERED_BY_OBJECT_NAME;
      } else {
        orderField = Constants.ORDERED_BY_LAST_UPDATE_TIME;
      }
    }

    return orderField;
  }

  /**
   * 构建orderType
   *
   * @param sort
   * @return
   */
  private String buildOrderType(Integer sort) {
    // 排序，1:按修改时间倒序、2:按修改时间升序、3:按对象名称升序、4:按对象名称倒序
    String orderType = Constants.ORDERED_DESC;
    if (sort != null) {
      if (Constants.ORDERED_BY_MODIFIED_TIME_DESC == sort) {
        orderType = Constants.ORDERED_DESC;
      } else if (Constants.ORDERED_BY_MODIFIED_TIME_ASC == sort) {
        orderType = Constants.ORDERED_ASC;
      } else if (Constants.ORDERED_BY_OBJECT_NAME_ASC == sort) {
        orderType = Constants.ORDERED_ASC;
      } else if (Constants.ORDERED_BY_OBJECT_NAME_DESC == sort) {
        orderType = Constants.ORDERED_DESC;
      } else {
        orderType = Constants.ORDERED_DESC;
      }
    }

    return orderType;
  }

  /**
   * 根据桶策略查询对象
   *
   * @param sort
   * @param pageNo
   * @param pageSize
   * @param bucketName
   * @param prefix
   * @param objectName
   * @return
   */
  private QueryRespBean<ObjectDto> exeQueryObjectList(
      Integer sort, int pageNo, int pageSize, String bucketName, String prefix, String objectName) {
    QueryRespBean<ObjectDto> queryRespBean = new QueryRespBean<>();
    String orderField = buildOrderField(sort);
    String orderType = buildOrderType(sort);

    // step1:获取bucket元数据
    BucketDto queryBucketDto = queryBucketDto(bucketName);

    // step2:根据桶策略处理
    Page<ObjectDto> objectList = null;
    StrategyEnum bucketStrategy = StrategyEnum.getStrategyType(queryBucketDto.getBucketStrategy());
    prefix = prefix == null ? Constants.BLACK : prefix;
    com.github.pagehelper.PageHelper.startPage(pageNo, pageSize);
    if (!getUserId().equals(queryBucketDto.getCreatedBy())
        && bucketStrategy == StrategyEnum.PRIVATE) {
      objectList =
          this.objectDao.getObjectListFromPrivateBucket(
              bucketName, prefix, objectName, getUserId(), orderField, orderType);
    } else {
      objectList =
          this.objectDao.getObjectListFromPublicBucket(
              bucketName, prefix, objectName, getUserId(), orderField, orderType);
    }

    queryRespBean.setResult(objectList);

    return queryRespBean;
  }

  /**
   * 获取minio连接信息
   *
   * @return
   */
  private MinioClient getMinioClient() {
    if (minioClient == null) {
      endPoint = ConfigUtils.getInstance().getString("minio.endPoint.url");
      accessKey = ConfigUtils.getInstance().getString("minio.username");
      secretKey = ConfigUtils.getInstance().getString("minio.passward");
      minioClient = MinioClient.builder().endpoint(endPoint).credentials(accessKey, secretKey).build();
    }

    return minioClient;
  }

  /**
   * 记录统计信息，包括容量、对象数量、读写流量等
   *
   * @param bucketDto
   * @param objectId
   * @param oldObjectSize
   * @param newObjectSize
   * @param action
   */
  private void recordStatsInfo(
      BucketDto bucketDto,
      String objectId,
      long oldObjectSize,
      long newObjectSize,
      AcitonEnum action) {
    // step1:bucket存量统计信息持久化
    recordStorageStats(bucketDto, oldObjectSize, newObjectSize, action);

    // step2:记录PUT、GET请求
    recordMetricStats(bucketDto, objectId, newObjectSize, action);
  }

  /**
   * 记录存量统计信息
   *
   * @param bucketDto
   * @param oldObjectSize
   * @param newObjectSize
   * @param action
   */
  private void recordStorageStats(
      BucketDto bucketDto, long oldObjectSize, long newObjectSize, AcitonEnum action) {
    switch (action) {
      case PUT_OBJECT: // 上传对象
        bucketDto.setObjectNum(bucketDto.getObjectNum() + 1);
        bucketDto.setCapacity(bucketDto.getCapacity() + newObjectSize);
        break;
      case REPEAT_PUT_OBJECT: // 重复上传对象
        bucketDto.setCapacity(bucketDto.getCapacity() + newObjectSize - oldObjectSize);
        break;
      case REMOVE_OBJECT: // 删除对象
        bucketDto.setObjectNum(bucketDto.getObjectNum() - 1);
        bucketDto.setCapacity(bucketDto.getCapacity() - newObjectSize);
        break;
      default: // 其他Action不统计
        return;
    }

    this.bucketService.updateByPrimaryKey(bucketDto);
  }

  /**
   * 记录指标统计信息
   *
   * @param bucketDto
   * @param objectId
   * @param objectSize
   * @param action
   */
  private void recordMetricStats(
      BucketDto bucketDto, String objectId, long objectSize, AcitonEnum action) {
    // bucket审计日志中action
    String auditAction = null;
    // bucket审计日志中实际流量大小
    long realTraffic = 0L;

    switch (action) {
      case PUT_OBJECT: // 上传对象
        auditAction = action.getAction();
        realTraffic = objectSize;
        break;
      case REPEAT_PUT_OBJECT: // 重复上传对象
        auditAction = AcitonEnum.PUT_OBJECT.getAction();
        realTraffic = objectSize;
        break;
      case GET_OBJECT: // 下载对象
        auditAction = action.getAction();
        realTraffic = objectSize;
        break;
      default: // 其他Action不统计
        return;
    }

    BucketAuditDto bucketAuditDto =
        BucketAuditDto.builder()
            .id(ComUtil.getId())
            .bucketId(bucketDto.getId())
            .objectId(objectId)
            .stamp(System.currentTimeMillis())
            .action(auditAction)
            .traffic(realTraffic)
            .build();
    this.bucketAuditService.insert(bucketAuditDto);
  }

  /**
   * 列举当前目录下所有对象
   *
   * @param objectDto
   * @return
   */
  private List<ObjectDto> listObjectsOfCurrDir(ObjectDto objectDto) {
    String orderField = Constants.ORDERED_BY_LAST_UPDATE_TIME;
    String orderType = Constants.ORDERED_DESC;

    /** 当前对象是目录，prefix+objectName作为前缀查询是否有对象，如果有证明目录非空 */
    String prefix = MinioUtils.buildFileName(objectDto.getPrefix(), objectDto.getObjectName());
    Page<ObjectDto> objectList =
        this.objectDao.getObjectList(
            orderField, orderType, objectDto.getBucketName(), prefix, null);

    return objectList.getResult();
  }

  /**
   * 递归删除对象
   *
   * @param objectDto
   */
  private void recursiveDeleteObject(BucketDto bucketDto, ObjectDto objectDto) {
    if (objectDto.isDirectory()) {
      List<ObjectDto> objectDtos = listObjectsOfCurrDir(objectDto);
      for (ObjectDto obj : objectDtos) {
        recursiveDeleteObject(bucketDto, obj);
      }

      removeObjectInner(bucketDto, objectDto);
    } else {
      removeObjectInner(bucketDto, objectDto);
    }
  }

  /**
   * 删除对象内部函数
   *
   * @param objectDto
   */
  private void removeObjectInner(BucketDto bucketDto, ObjectDto objectDto) {
    // step1:构建对象名
    String finalObjectName = null;
    if (objectDto.isDirectory()) {
      finalObjectName =
          MinioUtils.buildDirectoryName(objectDto.getPrefix(), objectDto.getObjectName());
    } else {
      finalObjectName = MinioUtils.buildFileName(objectDto.getPrefix(), objectDto.getObjectName());
    }

    // step2:删除元数据
    // step2-1:删除对象元数据
    this.objectDao.deleteByPrimaryKey(objectDto.getId());
    // step2-2:删除对象-用户映射关系
    this.bucketAuthService.delBucketAuth(objectDto);

    // step3:删除对象
    MinioUtils.removeObject(getMinioClient(), objectDto.getBucketName(), finalObjectName);

    // step4:统计概要信息
    if (!objectDto.isDirectory()) {
      recordStatsInfo(
          bucketDto, objectDto.getId(), 0L, objectDto.getObjectSize(), AcitonEnum.REMOVE_OBJECT);
    }
  }

  /**
   * 根据用户ID获取用户名
   *
   * @param userIds
   * @return
   * @throws Exception
   */
  public String getUserNameByIds(String userIds) {
    if (StringUtils.isEmpty(userIds)) {
      return null;
    }

    Map<String, Set<String>> convertFormData = new HashMap<>();
    String[] array = userIds.split(",");
    for (String userId : array) {
      BusinessUtil.createConvertSet(convertFormData, SystemConstant.USER, userId);
    }

    StringBuffer buf = new StringBuffer(Constants.BLACK);
    if (convertFormData.size() > 0) {
      // 获取请求结果
      Map<String, Map<String, String>> convertResultData =
          convertColumnClient.replace(convertFormData);
      for (String userId : array) {
        String name = BusinessUtil.convertFormat(convertResultData, SystemConstant.USER, userId);
        buf.append(name).append(Constants.COMMA);
      }
    }

    String names = buf.toString();
    if (names.length() >= 1) {
      names = names.substring(0, names.length() - 1);
    }
    return names;
  }

  /**
   * 计算分享对象URL失效时间
   *
   * @param expire
   * @return
   */
  private int calExpireTime(Integer expire) {
    if (expire == null) {
      return Constants.OBJECT_DEFAULT_EXPIRIED_TIME;
    }

    if (expire > Constants.OBJECT_DEFAULT_EXPIRIED_TIME) {
      return Constants.OBJECT_DEFAULT_EXPIRIED_TIME;
    }

    if (expire < 60) {
      return Constants.OBJECT_MIN_EXPIRED_TIME;
    }

    return expire;
  }

  /**
   * 对象名称合法性检查
   *
   * @param objectName
   */
  private void checkObjectNameValid(String objectName) {
    if (StringUtils.isEmpty(objectName)) {
      CheckParaUtils.printLogAndThrowException(logger, "目录或对象名称为空.");
    }

    // 注意：对象或目录名称可能含有中文，minio对象名称大小限制为255bytes，所以此处因检查字节数最合理
    if (objectName.getBytes().length > Constants.OBJECT_NAME_MAX_LENGTH) {
      CheckParaUtils.printLogAndThrowException(logger, "目录或对象名称不超过255个字符.");
    }
  }
}
